#ifndef CSV_HPP
#define CSV_HPP

#include <cstdlib>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <stdlib.h>
#include "aes_encrypt.hpp"

#ifdef WIN32
#define atoi64 _atoi64
#else
#define atoi64 atoll
#endif

#define AES_ENCRYPT_KEY_SIZE        128
#define AES_ENCRYPT_BLOCK_SIZE      160
typedef aes_encrypt<AES_ENCRYPT_KEY_SIZE, AES_ENCRYPT_BLOCK_SIZE> aes_encrypt128;

class csv;

template <typename T>
inline void csv_read_stream(const csv& stream, T& value);

class csv
{
public:

  csv() : data_pos_row_(0), data_pos_col_(0)
  {

  }

	bool load(std::string file_name, char quote = '\t', bool skip_head = false)
	{
		std::ifstream file(file_name.c_str());
		if (!file.is_open())
		{
			return false;
		}

		std::string csv_line;
		while (std::getline(file, csv_line))
		{
			if (skip_head)
			{
				skip_head = false;
				continue;
			}

      if ((csv_line.size() > 0) && (csv_line[csv_line.size() - 1] == '\r'))
        csv_line.resize(csv_line.size() - 1);

			std::istringstream csv_stream(csv_line);
			std::vector<std::string> csv_row;
			std::string csv_element;
			while (std::getline(csv_stream, csv_element, quote))
			{
				csv_row.push_back(csv_element);
			}

			if (!csv_row.empty())
				csv_data.push_back(csv_row);
		}

		return true;
	}

  bool load_from_string(std::string str_buf, char quote = '\t', bool skip_head = false)
  {
    std::istringstream file(str_buf);

    std::string csv_line;
    while (std::getline(file, csv_line))
    {
      if (skip_head)
      {
        skip_head = false;
        continue;
      }

      if ((csv_line.size() > 0) && (csv_line[csv_line.size() - 1] == '\r'))
        csv_line.resize(csv_line.size() - 1);

      std::istringstream csv_stream(csv_line);
      std::vector<std::string> csv_row;
      std::string csv_element;
      while (std::getline(csv_stream, csv_element, quote))
      {
        csv_row.push_back(csv_element);
      }

      if (!csv_row.empty())
        csv_data.push_back(csv_row);
    }

    return true;
  }

	unsigned int get_row_num()
	{
		return (unsigned int)csv_data.size();
	}

	bool is_exist(unsigned int row, unsigned int column)
	{
		if (row >= csv_data.size()) return false;
		std::vector<std::string>& csv_row = csv_data[row];
		if (column >= csv_row.size()) return false;
		
		return true;
	}

	const char* operator () (unsigned int row, unsigned int column) const
	{
		if (row >= csv_data.size()) return 0;
		const std::vector<std::string>& csv_row = csv_data[row];
		if (column >= csv_row.size()) return 0;
		
		return csv_row[column].c_str();
	}

	bool get_int(unsigned int row, unsigned int column, int &result)
	{
		if (!is_exist(row, column)) return false;

		result = atoi((*this)(row, column));
		return true;
	}

  bool get_uint(unsigned int row, unsigned int column, unsigned int &result)
  {
    if (!is_exist(row, column)) return false;

    result = strtoul((*this)(row, column), 0, 10);
    return true;
  }

	bool get_longlong(unsigned int row, unsigned int column, long long int& result)
	{
		if (!is_exist(row, column)) return false;

		result = atoi64((*this)(row, column));
		return true;
	}

	bool get_double(unsigned int row, unsigned int column, double& result)
	{
		if (!is_exist(row, column)) return false;

		result = atof((*this)(row, column));
		return true;
	}

	bool get_string(unsigned int row, unsigned int column, std::string &result)
	{
		if (!is_exist(row, column)) return false;

		result = std::string((*this)(row, column));
		return true;
	}

	int get_int_value(unsigned int row, unsigned int column)
	{
		int result;
		if (!get_int(row, column, result))
			return 0;
		return result;
	}

	std::string get_string_value(unsigned int row, unsigned int column)
	{
		std::string result;
		if (!get_string(row, column, result))
			return "";
		return result;
	}

  template <typename T>
  inline void read(T& value) const
  {
    csv_read_stream<T>(*this, value);
  }

  const char* read_string() const
  {
    if (csv_data.empty() || csv_data[0].empty())
      return 0;

    if (data_pos_col_ >= (int)csv_data[0].size())
    {
      data_pos_col_ = 0;
      data_pos_row_++;
    }

    const char* str = (*this)(data_pos_row_, data_pos_col_);

    data_pos_col_++;

    return str;
  }

  std::string read_string_ex() const
  {
    const char* str = read_string();
    if (str == 0)
      return std::string();

    return str;
  }

  int read_int() const
  {
    const char* str = read_string();
    if (str == 0)
      return 0;

    return atoi(str);
  }

  unsigned int read_uint() const
  {
    const char* str = read_string();
    if (str == 0)
      return 0;

    return strtoul(str, 0, 10);
  }

  long read_long() const
  {
    const char* str = read_string();
    if (str == 0)
      return 0;

    return atol(str);
  }

  long long read_longlong() const
  {
    const char* str = read_string();
    if (str == 0)
      return 0;

    return atoi64(str);
  }

  double read_float() const
  {
    const char* str = read_string();
    if (str == 0)
      return 0;

    return atof(str);
  }

  void clear()
  {
    csv_data.clear();
    data_pos_row_ = 0;
    data_pos_col_ = 0;
  }

private:

	std::vector< std::vector<std::string> > csv_data;

  mutable int data_pos_row_;
  mutable int data_pos_col_;
};


//////////////////////////////////////////////////////////////////////////

template <typename T>
inline void csv_read_stream(const csv& stream, T& value)
{
  stream.read_string();
  printf("!!!!! csv read stream error");
}

//////////////////////////////////////////////////////////////////////////

template <>
inline void csv_read_stream<bool>(const csv& stream, bool& value)
{
  value = (stream.read_int() != 0);
}

template <>
inline void csv_read_stream<char>(const csv& stream, char& value)
{
  value = (char)stream.read_int();
}

template <>
inline void csv_read_stream<unsigned char>(const csv& stream, unsigned char& value)
{
  value = (unsigned char)stream.read_int();
}

template <>
inline void csv_read_stream<short>(const csv& stream, short& value)
{
  value = (short)stream.read_int();
}

template <>
inline void csv_read_stream<unsigned short>(const csv& stream, unsigned short& value)
{
  value = (unsigned short)stream.read_int();
}

template <>
inline void csv_read_stream<int>(const csv& stream, int& value)
{
  value = stream.read_int();
}

template <>
inline void csv_read_stream<unsigned int>(const csv& stream, unsigned int& value)
{
  value = stream.read_uint();
}

template <>
inline void csv_read_stream<long>(const csv& stream, long& value)
{
  value = stream.read_long();
}

template <>
inline void csv_read_stream<unsigned long>(const csv& stream, unsigned long& value)
{
  value = (unsigned long)stream.read_long();
}

template <>
inline void csv_read_stream<long long>(const csv& stream, long long& value)
{
  value = stream.read_longlong();
}

template <>
inline void csv_read_stream<unsigned long long>(const csv& stream, unsigned long long& value)
{
  value = (unsigned long long)stream.read_longlong();
}

template <>
inline void csv_read_stream<float>(const csv& stream, float& value)
{
  value = (float)stream.read_float();
}

template <>
inline void csv_read_stream<double>(const csv& stream, double& value)
{
  value = stream.read_float();
}

template <>
inline void csv_read_stream<std::string>(const csv& stream, std::string& value)
{
  value = stream.read_string_ex();
}


//////////////////////////////////////////////////////////////////////////

template <class T>
class csv_ex : public csv
{
	typedef std::multimap<T, unsigned int> row_index;
	typedef typename row_index::iterator row_index_iter;

public:

	bool load(std::string file_name, char quote = '\t', unsigned int column = 0, bool skip_head = false)
	{
		if (csv::load(file_name, quote, skip_head))
		{
			T temp;
			return build_index(column, temp);
		}

		return false;
	}

  bool load_from_string(std::string str_buf, char quote = '\t', unsigned int column = 0, bool skip_head = false)
  {
    if (csv::load_from_string(str_buf, quote, skip_head))
    {
      T temp;
      return build_index(column, temp);
    }

    return false;
  }

	std::vector<unsigned int> get_rows_by_index(T& value)
	{
		std::vector<unsigned int> ret;

		std::pair<row_index_iter, row_index_iter> iter_pair = row_index_.equal_range(value);
		for (row_index_iter iter = iter_pair.first; iter!= iter_pair.second; ++iter)
		{
			ret.push_back(iter->second);
		}

		return ret;
	}

  void clear_ex()
  {
    clear();
    row_index_.clear();
  }

private:

	bool build_index(unsigned int column, int& dummy)
	{
		int value;
		for (unsigned int i = 0; i < get_row_num(); ++i)
		{
			if (!get_int(i, column, value)) return false;

			row_index_.insert(std::make_pair(value, i));
		}

		return true;
	}

	bool build_index(unsigned int column, long long int& dummy)
	{
		long long int value;
		for (unsigned int i = 0; i < get_row_num(); ++i)
		{
			if (!get_longlong(i, column, value)) return false;

			row_index_.insert(std::make_pair(value, i));
		}

		return true;
	}

	bool build_index(unsigned int column, double& dummy)
	{
		double value;
		for (unsigned int i = 0; i < get_row_num(); ++i)
		{
			if (!get_double(i, column, value)) return false;

			row_index_.insert(std::make_pair(value, i));
		}

		return true;
	}

	bool build_index(unsigned int column, std::string& dummy)
	{
		std::string value;
		for (unsigned int i = 0; i < get_row_num(); ++i)
		{
			if (!get_string(i, column, value)) return false;

			row_index_.insert(std::make_pair(value, i));
		}

		return true;
	}

private:

	row_index row_index_;
};

#endif // CSV_HPP

